package nachos.threads;

import nachos.machine.*;
import java.util.LinkedList;

/**
 * Uses the hardware timer to provide preemption, and to allow threads to sleep
 * until a certain time.
 */
public class Alarm {

    /**
     * Allocate a new Alarm. Set the machine's timer interrupt handler to this
     * alarm's callback.
     *
     * <p><b>Note</b>: Nachos will not function correctly with more than one
     * alarm.
     */
    public Alarm() {
        Machine.timer().setInterruptHandler(new Runnable() {
            public void run() {
                timerInterrupt();
            }
        });
    }

    /**
     * The timer interrupt handler. This is called by the machine's timer
     * periodically (approximately every 500 clock ticks). Causes the current
     * thread to yield, forcing a context switch if there is another thread that
     * should be run.
     */
    public void timerInterrupt() {
        boolean intStatus = Machine.interrupt().disable();
        long instTime = Machine.timer().getTime();
        
        for (int i = 0; i < so1_waitingThreads.size(); i++) {
            if (instTime <= so1_waitingTime.get(i)) {
                KThread so1_currentThread = so1_waitingThreads.get(i);
                
                so1_waitingThreads.remove(so1_currentThread);
                so1_waitingTime.remove(so1_waitingTime.get(i));
                
                so1_currentThread.ready();
            }
        }

        Machine.interrupt().restore(intStatus);
    }

    /**
     * Put the current thread to sleep for at least <i>x</i> ticks, waking it up
     * in the timer interrupt handler. The thread must be woken up (placed in
     * the scheduler ready set) during the first timer interrupt where
     *
     * <p><blockquote>
     * (current time) >= (WaitUntil called time)+(x)
     * </blockquote>
     *
     * @param	x	the minimum number of clock ticks to wait.
     *
     * @see	nachos.machine.Timer#getTime()
     */
    public void waitUntil(long x) {
        boolean intStatus = Machine.interrupt().disable();

        // Place current thread on a wait queue and put it to sleep

        so1_waitingThreads.add(KThread.currentThread());
        so1_waitingTime.add(Machine.timer().getTime() + x);
        KThread.currentThread().sleep();

        Machine.interrupt().restore(intStatus);
    }
    LinkedList<Long> so1_waitingTime = new LinkedList<Long>();
    LinkedList<KThread> so1_waitingThreads = new LinkedList<KThread>();
}
